﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
using System.Reflection;
using System.IO;
using System.Diagnostics;
namespace RoslynEval
{
    public class Eval
    {
        Assembly assembly;
        Type assemblyType;
        MethodInfo evaluator;

        public static Func<Double[], Double> CreateEvalFunction(String inEquation, String[] variableNames)
        {
            Eval eval = new Eval(inEquation, variableNames);
            return (Double[] variableValues) => eval.CallAssembly(variableValues);
        }

        public Eval(String inEquation, String[] variableNames)
        {
            SetAssembly(inEquation, variableNames);
        }

        public void SetAssembly(String inEquation,String[] variableNames)
        {
            String variableNamesString = VariableNamesToString(variableNames);
            var text = "using System; class Calc { public static object Eval(" + variableNamesString + ") { return " + inEquation + "; } }";
            assembly = CompileAssembly(text);
            if (assembly == null)
            {
                return;
            }
            assemblyType = assembly.GetType("Calc");
            evaluator = assemblyType.GetMethod("Eval");
        }

        public Double CallAssembly(Double[] variableValues)
        {
            if (assembly == null)
            {
                return Double.NaN;
            }
            object[] objs = Functional.Maps.Map(variableValues, x => (object)x);
            string answer = evaluator.Invoke(null, objs).ToString();
            return StringToDouble(answer);
        }

        public static Double StringToDouble(String inString)
        {
            Double result;
            if (Double.TryParse(inString, out result) == true)
            {
                return result;
            }
            else
            {
                return Double.NaN;
            }
        }

        public static String VariableNamesToString(String[] variableNames)
        {
            Int32 L;
            String lOut="";
            for (L = 0; L < variableNames.Length; L++)
            {
                lOut += "Double " + variableNames[L];
                if (L < variableNames.Length - 1)
                {
                    lOut += ",";
                }
            }
            return lOut;
        }

        public static Assembly CompileAssembly(String inCode)
        {

            var tree = SyntaxTree.ParseCompilationUnit(inCode);

            var compilation = Compilation.Create(

                "calc.dll",

                options: new CompilationOptions(assemblyKind: AssemblyKind.DynamicallyLinkedLibrary),

                syntaxTrees: new[] { tree },

                references: new[] { new AssemblyFileReference(typeof(object).Assembly.Location) });



            Assembly compiledAssembly;

            var stream = new MemoryStream();

            {

                EmitResult compileResult = compilation.Emit(stream);

                try
                {
                    compiledAssembly = Assembly.Load(stream.GetBuffer());
                }
                catch
                {
                    return null;
                }
                stream = null;
            }

            return compiledAssembly;
        }


    }
}
